package com.pivotal.cloud.security.password;

import com.pivotal.cloud.crypto.argon2.Argon2;
import com.pivotal.cloud.crypto.keygen.BytesKeyGenerator;
import com.pivotal.cloud.crypto.keygen.KeyGenerators;

import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @className: Argon2PasswordEncoder
 * @projectName: PivotalCloud项目
 * @module: PivotalCloud项目-Argon2PasswordEncoder类，主要位于Security安全模块-Argon2模式密码加密模块
 * @content: Argon2PasswordEncoder-Argon2模式密码加密
 * @author: Powered by Marklin
 * @datetime: 2025-06-24 13:27
 * @version: 1.0.0
 * @copyright: Copyright © 2018-2025 PivotalCloud Systems Incorporated. All rights
 * reserved.
 */
public class Argon2PasswordEncoder implements PasswordEncoder {

	private static final int DEFAULT_SALT_LENGTH = 16;

	private static final int DEFAULT_HASH_LENGTH = 32;

	private static final int DEFAULT_PARALLELISM = 1;

	private static final int DEFAULT_MEMORY = 1 << 14;

	private static final int DEFAULT_ITERATIONS = 2;

	/**
	 * 实例化对象
	 */
	private static final PasswordEncoder INSTANCE = Argon2PasswordEncoder.newInstance();

	private final Logger logger = LoggerFactory.getLogger(getClass());

	private final int hashLength;

	private final int parallelism;

	private final int memory;

	private final int iterations;

	private final BytesKeyGenerator saltGenerator;

	/**
	 * Constructs an Argon2 password encoder with the provided parameters.
	 * @param saltLength the salt length (in bytes)
	 * @param hashLength the hash length (in bytes)
	 * @param parallelism the parallelism
	 * @param memory the memory cost
	 * @param iterations the number of iterations
	 */
	public Argon2PasswordEncoder(int saltLength, int hashLength, int parallelism, int memory, int iterations) {
		this.hashLength = hashLength;
		this.parallelism = parallelism;
		this.memory = memory;
		this.iterations = iterations;
		this.saltGenerator = KeyGenerators.secureRandom(saltLength);
	}

	/**
	 * 获取实例
	 * @return 返回结果
	 */
	public static PasswordEncoder getInstance() {
		return INSTANCE;
	}

	/**
	 * 实例化对象
	 * @return 返回结果
	 */
	public static PasswordEncoder newInstance() {
		return defaultPasswordEncoder();
	}

	/**
	 * Constructs an Argon2 password encoder with a salt length of 16 bytes, a hash length
	 * of 32 bytes, parallelism of 1, memory cost of 1 << 14 and 2 iterations.
	 * @return the {@link Argon2PasswordEncoder}
	 *
	 * @since 5.8
	 */
	public static Argon2PasswordEncoder defaultPasswordEncoder() {
		return new Argon2PasswordEncoder(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH, DEFAULT_PARALLELISM, DEFAULT_MEMORY,
				DEFAULT_ITERATIONS);
	}

	private static boolean constantTimeArrayEquals(byte[] expected, byte[] actual) {
		if (expected.length != actual.length) {
			return false;
		}
		int result = 0;
		for (int i = 0; i < expected.length; i++) {
			result |= expected[i] ^ actual[i];
		}
		return result == 0;
	}

	/**
	 * 密码加密
	 * @param rawPassword 原始密码
	 * @return 返回结果
	 */
	@Override
	public String encode(CharSequence rawPassword) {
		byte[] salt = this.saltGenerator.generateKey();
		byte[] hash = new byte[this.hashLength];
		Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id).withSalt(salt)
				.withParallelism(this.parallelism).withMemoryAsKB(this.memory).withIterations(this.iterations).build();
		Argon2BytesGenerator generator = new Argon2BytesGenerator();
		generator.init(params);
		generator.generateBytes(rawPassword.toString().toCharArray(), hash);
		return Argon2.encode(hash, params);
	}

	/**
	 * 验证密码
	 * @param rawPassword 原始密码
	 * @param encodedPassword 加密密码
	 * @return 返回结果
	 */
	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (encodedPassword == null) {
			this.logger.warn("password hash is null");
			return false;
		}
		Argon2.Argon2Hash decoded;
		try {
			decoded = Argon2.decode(encodedPassword);
		}
		catch (IllegalArgumentException ex) {
			this.logger.warn("Malformed password hash", ex);
			return false;
		}
		byte[] hashBytes = new byte[decoded.getHash().length];
		Argon2BytesGenerator generator = new Argon2BytesGenerator();
		generator.init(decoded.getParameters());
		generator.generateBytes(rawPassword.toString().toCharArray(), hashBytes);
		return constantTimeArrayEquals(decoded.getHash(), hashBytes);
	}

	/**
	 * 更新密码编码
	 * @param encodedPassword 加密密码
	 * @return 返回结果
	 */
	@Override
	public boolean upgradeEncoding(String encodedPassword) {
		if (encodedPassword == null || encodedPassword.length() == 0) {
			this.logger.warn("password hash is null");
			return false;
		}
		Argon2Parameters parameters = Argon2.decode(encodedPassword).getParameters();
		return parameters.getMemory() < this.memory || parameters.getIterations() < this.iterations;
	}

}
